import os

from pyparsing import *

try:
   from . import exception
   from . import stdlib
   from . import node
except ImportError:
   import exception
   import stdlib
   import node

class Compile(object):
   variables = set()
   expression = set()
   def __init__(self):
      super(Compile, self).__init__()
      self._exprStack = []
      self._varStack = []

   def pushFirst(self, string, location, tokens):
      Compile.expression.add(tokens[0])

   def parseInteger(self, string, location, tokens):
      return node.NodeInteger(tokens[0])

   def parseFloat(self, string, location, tokens):
      return node.NodeFloat(tokens[0])

   def parseString(self, string, location, tokens):
      return node.NodeString(tokens[0])

   def parseAssign(self, string, location, tokens):
      # Compile.variable.add(tokens[0])
      if '=' in tokens:
         if tokens[1] == '=':
            tokens[2].name = tokens[0]
            Compile.variable.add(tokens[2])

class Lexer(object):
   builtins = set()
   keywords = set()

   def __init__(self, InputFile, Stdin = False):
      super(self.__class__, self).__init__()
      self._fh = None
      self._file = None
      self._stdin = False
      self._compile = Compile()

      if InputFile:
         self._file = InputFile
      else:
         self.error("no filname provided")

      if Stdin:
         self._stdin = True

      self._lb, self._rb = map(Suppress, "{}")
      self._lp, self._rp = map(Suppress, "()")
      self._lbk, self._rbk = map(Suppress, "[]")
      self._lt, self._gt = map(Suppress, "<>")
      self._quoted = quotedString.copy().setParseAction(
            removeQuotes).setParseAction(self._compile.parseString)

   def error(self, fmt, *args):
      mesg = stdlib.sprintf(fmt, *args)
      raise exception.LexerError(str(mesg))

   def __str__(self):
      return stdlib.my_str(self._file)

   def keyword(self, String = None, issuppress = False):
      retval = None
      Lexer.keywords.add(String)
      if issuppress:
         retval = Keyword(String).suppress()
      else:
         retval = Keyword(String)

      return retval

   def parse(self):
      ParserElement.setDefaultWhitespaceChars('\t ')
      kword = self.keyword
      newline = LineEnd().suppress()
      point = Literal('.')
      assignop = oneOf("= += -= *= |= &= ^= + - | & ^")
      exp = CaselessLiteral('E')
      operators = oneOf("= - + == ++ -- += -= / * *= | & |= &= ^ ^=")
      integer = Combine(Optional(oneOf("- +")) + Word(nums)).setParseAction(
            self._compile.parseInteger)
      identifier = Word(alphanums + "-_")
      real = Combine(Word(nums) + '.' +  Word(nums) + Optional(
         exp + integer)).setParseAction(self._compile.parseFloat)

      plus  = Literal( "+" )
      minus = Literal( "-" )
      mult  = Literal( "*" )
      div   = Literal( "/" )

      addop  = plus | minus
      multop = mult | div
      expop = Literal( "^" )
      assign = Literal( "=" )

      expr = Forward()
      atom = ((exp | real | integer | identifier).setParseAction(
         self._compile.pushFirst) | (self._lp + expr.suppress() + self._rp) +
         newline)
      factor = Forward()
      factor << atom + ZeroOrMore((expop + factor).setParseAction(
         self._compile.pushFirst)) + newline

      term = factor  + ZeroOrMore((multop + factor).setParseAction(
         self._compile.pushFirst)) + newline
      expr << term + ZeroOrMore((addop + term).setParseAction(
         self._compile.pushFirst)) + newline

      printvars = (real | integer | identifier | self._quoted)
      printstmt = Group(
            kword('print') + self._lp + printvars + self._rp + newline)
      stringstmt  = (identifier +
            oneOf("= += +") + (identifier | self._quoted)) + ZeroOrMore(
                  addop + identifier) 
      assignstmt = (Optional(identifier + assign).setParseAction(
            self._compile.parseAssign) + expr + newline) | (
                  stringstmt + newline)

      pythonComment = '#' + restOfLine + newline
      rule = OneOrMore(printstmt | assignstmt) + StringEnd() + newline
      rule.ignore(Literal("\\") + LineEnd())
      rule.ignore(pythonComment)
      DWC = "".join(x for x in rule.DEFAULT_WHITE_CHARS if x != "\n")
      rule.setDefaultWhitespaceChars(DWC)

      results = None
      try:
         if self._stdin:
            results = rule.parseString(self._file.read())
         else:
            with open(self._file, "r") as fh:
               results = rule.parseString(fh.read())
      except ParseFatalException as err:
         self.error("parsing failed on %s: %s", self._file, err)
      except ParseException as err:
         self.error("syntax error on line %d in file %s: %s",
               err.lineno, self._file, err.msg)
             
      return results
